/***************************************************************************
 *   Copyright (C) 2009 - 2010 by Simon Qian <SimonQian@SimonQian.com>     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "vsf.h"
#include "core.h"

#define STM32_HSI_FREQ_HZ				(8 * 1000 * 1000)
#define STM32_UID_ADDR					0x1FFFF7E8
#define STM32_UID_SIZE					12

static struct vsfhal_info_t vsfhal_info = 
{
	0, CORE_VECTOR_TABLE, CORE_CLKEN, CORE_CLKSRC, CORE_PLLSRC, CORE_RTCSRC, CORE_HSE_TYPE, CORE_DEBUG,
	HSE_FREQ_HZ, CORE_PLL_FREQ_HZ, CORE_AHB_FREQ_HZ, CORE_APB1_FREQ_HZ, CORE_APB2_FREQ_HZ
};

vsf_err_t vsfhal_core_get_info(struct vsfhal_info_t **info)
{
	*info = &vsfhal_info;
	return VSFERR_NONE;
}

vsf_err_t vsfhal_core_reset(void *p)
{
	NVIC_SystemReset();
	return VSFERR_NONE;
}

vsf_err_t vsfhal_core_init(void *p)
{
	int tmp32;

	if (p != NULL)
		vsfhal_info = *(struct vsfhal_info_t *)p;

	RCC->CR |= RCC_CR_HSION;
	RCC->CFGR &= ~(RCC_CFGR_SW | RCC_CFGR_HPRE | RCC_CFGR_PPRE1 |
		RCC_CFGR_PPRE2 | RCC_CFGR_ADCPRE | RCC_CFGR_USBPRE | RCC_CFGR_MCO);
	RCC->CR &= ~(RCC_CR_HSEON | RCC_CR_CSSON | RCC_CR_PLLON
#ifdef STM32F10X_CL
		RCC_CR_PLL2ON | RCC_CR_PLL3ON
#endif
		);
	RCC->CR &= ~RCC_CR_HSEBYP;
	RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL);
#if defined(STM32F10X_CL) || defined(STM32F10X_LD_VL) || defined(STM32F10X_MD_VL) || defined(STM32F10X_HD_VL)
	RCC->CFGR2 &= ~RCC_GCFGR2_HSEPREDV1;
#endif
	RCC->CIR = 0;

	switch (vsfhal_info.clksrc)
	{
	case STM32_CLKSRC_HSE:
		vsfhal_info.sys_freq_hz = vsfhal_info.osc_freq_hz;
		break;
	case STM32_CLKSRC_HSI:
		vsfhal_info.sys_freq_hz = STM32_HSI_FREQ_HZ;
		break;
	case STM32_CLKSRC_PLL:
		vsfhal_info.sys_freq_hz = vsfhal_info.pll_freq_hz;
		break;
	default:
		return VSFERR_FAIL;
	}
	vsfhal_info.cpu_freq_hz = vsfhal_info.ahb_freq_hz;

	// set flash latency before any change in clock frequency
	if (vsfhal_info.sys_freq_hz >= 48000000)
		FLASH->ACR = FLASH_ACR_PRFTBE | 2;
	else if (vsfhal_info.sys_freq_hz >= 24000000)
		FLASH->ACR = FLASH_ACR_PRFTBE | 1;
	else
		FLASH->ACR = FLASH_ACR_PRFTBE | 0;

	// config ahb and apb before any change in clock frequency
	tmp32 = ffz(~(vsfhal_info.sys_freq_hz / vsfhal_info.ahb_freq_hz));
	if (tmp32 > 0)
		RCC->CFGR |= ((0x08 | tmp32) & 0x0F) << 4;
	tmp32 = ffz(~(vsfhal_info.sys_freq_hz / vsfhal_info.apb1_freq_hz));
	if (tmp32 > 0)
		RCC->CFGR |= ((0x04 | tmp32) & 0x03) << 8;
	tmp32 = ffz(~(vsfhal_info.sys_freq_hz / vsfhal_info.apb2_freq_hz));
	if (tmp32 > 0)
		RCC->CFGR |= ((0x04 | tmp32) & 0x03) << 11;

	if (vsfhal_info.clk_enable & STM32_CLK_HSI)
	{
		RCC->CR |= RCC_CR_HSION;
		while (!(RCC->CR & RCC_CR_HSIRDY));
	}

	if (vsfhal_info.clk_enable & STM32_CLK_HSE)
	{
		RCC->CR |= RCC_CR_HSEON;
		if (STM32_HSE_TYPE_CLOCK == vsfhal_info.hse_type)
			RCC->CR |= RCC_CR_HSEBYP;
		else
			RCC->CR &= ~RCC_CR_HSEBYP;
		while (!(RCC->CR & RCC_CR_HSERDY));
	}

	if (vsfhal_info.clk_enable & STM32_CLK_PLL)
	{
		uint32_t pllsrc_freq;
		switch (vsfhal_info.pllsrc)
		{
		case STM32_PLLSRC_HSE:
			RCC->CFGR |= RCC_CFGR_PLLSRC;
			pllsrc_freq = vsfhal_info.osc_freq_hz;
			break;
		case STM32_PLLSRC_HSEd2:
			RCC->CFGR |= RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE;
			pllsrc_freq = vsfhal_info.osc_freq_hz / 2;
			break;
		case STM32_PLLSRC_HSId2:
			pllsrc_freq = STM32_HSI_FREQ_HZ / 2;
			break;
		default:
			return VSFERR_FAIL;
		}

		pllsrc_freq = vsfhal_info.pll_freq_hz / pllsrc_freq;
		RCC->CFGR |= ((pllsrc_freq - 2) << 18);

		RCC->CR |= RCC_CR_PLLON;
		while (!(RCC->CR & RCC_CR_PLLRDY));
	}

	RCC->CFGR |= vsfhal_info.clksrc;
	while (((RCC->CFGR & RCC_CFGR_SWS) >> 2) != vsfhal_info.clksrc);

	RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
	AFIO->MAPR |= vsfhal_info.debug_setting;

	SCB->VTOR = vsfhal_info.vector_table;
	NVIC_SetPriorityGrouping(vsfhal_info.priority_group);
	return VSFERR_NONE;
}

uint32_t vsfhal_uid_get(uint8_t *buffer, uint32_t size)
{
	if (NULL == buffer)
		return 0;

	if (size > STM32_UID_SIZE)
		size = STM32_UID_SIZE;

	memcpy(buffer, (uint8_t *)STM32_UID_ADDR, size);
	return size;
}

#include "hal/arch/arm_cm/arm_cm.h"
